package com.xiashitech.agent.instrumentation.spring.controller.apicapture.inst.utils;

import com.xiashitech.agent.config.StartConfig;
import com.xiashitech.agent.constants.ApiParamDataType;
import com.xiashitech.interfaces.agent.dto.api.Params;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * 该类与全量api代码重合，但是考虑后续根据请求实时捕获无法从静态代码获取的api信息时，要对该类进行修改，所以不提取公共处理类。
 */
public class GenerateParamsUtil {

    private static ThreadLocal<Set<Class>> completedClass = ThreadLocal.withInitial(() -> new HashSet<Class>());

    public static void clearCompletedClass() {
        completedClass.get().clear();
    }

    public static List<Params> generateParams(Class<?> clazz) {
        List<Params> paramsList = new ArrayList();
        if(clazz == null) {
            return paramsList;
        }
        if (!completedClass.get().contains(clazz)) {
            completedClass.get().add(clazz);
        } else {
            return paramsList;
        }
        try {
            if("com.alibaba.fastjson.JSONObject".endsWith(clazz.getName())) {
                paramsList.add(Params.builder().name("jsonObject").type(ApiParamDataType.JSON_OBJECT.value()).build());
                return paramsList;
            }
            if("com.alibaba.fastjson.JSONArray".endsWith(clazz.getName())) {
                paramsList.add(Params.builder().name("jsonArray").type(ApiParamDataType.JSON_ARRAY.value()).build());
                return paramsList;
            }
            if(Collection.class.isAssignableFrom(clazz)) {
                paramsList.add(Params.builder().name(clazz.getSimpleName().toLowerCase()).type(ApiParamDataType.fromValue(clazz.getSimpleName())).build());
                return paramsList;
            }
            if(clazz.isArray()) {
                Class clazzActualType = clazz.getComponentType();
                paramsList.add(Params.builder().name(clazz.getSimpleName().toLowerCase()).type(ApiParamDataType.ARRAY.value()).subParams(generateParams(clazzActualType)).build());
                return paramsList;
            }
            if(Map.class.isAssignableFrom(clazz)) {
                paramsList.add(Params.builder().name(Map.class.getSimpleName().toLowerCase()).type(ApiParamDataType.MAP.value()).build());
                return paramsList;
            }
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if(declaredField.getType() == clazz) {
                    continue;
                }
                if (isPrimitiveOrWrap(declaredField.getType())) {
                    paramsList.add(Params.builder().name(declaredField.getName()).type(ApiParamDataType.fromValue(declaredField.getType().getSimpleName())).build());
                } else if(ApiParamDataType.judgeAnalogyPrimitive(declaredField.getType().getSimpleName())) {
                    paramsList.add(Params.builder().name(declaredField.getName()).type(ApiParamDataType.fromValue(declaredField.getType().getSimpleName())).build());
                } else if("com.alibaba.fastjson.JSONObject".endsWith(declaredField.getType().getName())) {
                    paramsList.add(Params.builder().name("jsonObject").type(ApiParamDataType.JSON_OBJECT.value()).build());
                } else if("com.alibaba.fastjson.JSONArray".endsWith(declaredField.getType().getName())) {
                    paramsList.add(Params.builder().name("jsonArray").type(ApiParamDataType.JSON_ARRAY.value()).build());
                } else if(Collection.class.isAssignableFrom(declaredField.getType())) {
                    Type genericType = declaredField.getGenericType();
                    if(genericType == null) {
                        continue;
                    }
                    if(genericType instanceof ParameterizedType) {
                        ParameterizedType parameterizedType = (ParameterizedType) genericType;
                        for (Type actualType:parameterizedType.getActualTypeArguments()) {
                            if(actualType instanceof Class) {
                                Class clazzActualType = (Class)actualType;
                                if(isPrimitiveOrWrap(clazzActualType) || ApiParamDataType.judgeAnalogyPrimitive(clazzActualType.getSimpleName())) {
                                    paramsList.add(Params.builder().name(declaredField.getName()).type(ApiParamDataType.fromValue(declaredField.getType().getSimpleName())).subParams(Arrays.asList(Params.builder().name(declaredField.getName()).type(ApiParamDataType.fromValue(clazzActualType.getSimpleName())).build())).build());
                                } else {
                                    paramsList.add(Params.builder().name(declaredField.getName()).type(ApiParamDataType.fromValue(declaredField.getType().getSimpleName())).subParams(generateParams(clazzActualType)).build());
                                }
                            }
                        }
                    }
                } else if(declaredField.getType().isArray()) {
                    Class clazzActualType = declaredField.getType().getComponentType();
                    paramsList.add(Params.builder().name(declaredField.getName()).type(ApiParamDataType.ARRAY.value()).subParams(generateParams(clazzActualType)).build());
                } else if(Map.class.isAssignableFrom(declaredField.getType())) {
                    paramsList.add(Params.builder().name(Map.class.getSimpleName().toLowerCase()).type(ApiParamDataType.MAP.value()).build());
                } else {
                    paramsList.add(Params.builder().name(declaredField.getName()).type(ApiParamDataType.OBJECT.value()).subParams(generateParams(declaredField.getType())).build());
                }
            }
            while (clazz != null){
                clazz = clazz.getSuperclass();
                paramsList.addAll(generateParams(clazz));
            }
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
        return paramsList;
    }

    public static boolean isPrimitiveOrWrap(Class clazz) {
        try {
            if(clazz == String.class) {
                return true;
            }
            if(clazz.isPrimitive()) {
                return true;
            }
            return ((Class)clazz.getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            return false;
        }
    }

}
